NumPy: векторы и операции над ними


В этом ноутбуке нам понадобятся библиотека NumPy. Для удобства импортируем ее под более коротким именем:


In [1]:
import numpy as np

1. Создание векторов

Самый простой способ создать вектор в NumPy — задать его явно с помощью numpy.array(list, dtype=None, ...).

Параметр list задает итерируемый объект, из которого можно создать вектор. Например, в качестве этого параметра можно задать список чисел. Параметр dtype задает тип значений вектора, например, float — для вещественных значений и int — для целочисленных. Если этот параметр не задан, то тип данных будет определен из типа элементов первого аргумента.


In [2]:
a = np.array([1, 2, 3, 4])
print 'Вектор:\n', a


Вектор:
[1 2 3 4]

In [3]:
b = np.array([1, 2, 3, 4, 5], dtype=float)
print 'Вещественный вектор:\n', b


Вещественный вектор:
[ 1.  2.  3.  4.  5.]

In [4]:
c = np.array([True, False, True], dtype=bool)
print 'Булевский вектор:\n', c


Булевский вектор:
[ True False  True]

Тип значений вектора можно узнать с помощью numpy.ndarray.dtype:


In [5]:
print 'Тип булевского вектора:\n', c.dtype


Тип булевского вектора:
bool

Другим способом задания вектора является функция numpy.arange(([start, ]stop, [step, ]...), которая задает последовательность чисел заданного типа из промежутка [start, stop) через шаг step:


In [6]:
d = np.arange(start=10, stop=20, step=2) # последнее значение не включается!
print 'Вектор чисел от 10 до 20 с шагом 2:\n', d


Вектор чисел от 10 до 20 с шагом 2:
[10 12 14 16 18]

In [7]:
f = np.arange(start=0, stop=1, step=0.3, dtype=float)
print 'Вещественный вектор чисел от 0 до 1 с шагом 0.3:\n', f


Вещественный вектор чисел от 0 до 1 с шагом 0.3:
[ 0.   0.3  0.6  0.9]

По сути вектор в NumPy является одномерным массивом, что соответствует интуитивному определению вектора:


In [8]:
print c.ndim # количество размерностей


1

In [9]:
print c.shape # shape фактически задает длину вектора


(3,)

Обратите внимание: _вектор _и одномерный массив тождественные понятия в NumPy. Помимо этого, также существуют понятия вектор-столбец и вектор-строка, которые, несмотря на то что математически задают один и тот же объект, являются двумерными массивами и имеют другое значение поля shape (в этом случае поле состоит из двух чисел, одно из которых равно единице). Эти тонкости будут рассмотрены в следующем уроке.

Более подробно о том, как создавать векторы в NumPy, см. документацию.

2. Операции над векторами

Векторы в NumPy можно складывать, вычитать, умножать на число и умножать на другой вектор (покоординатно):


In [10]:
a = np.array([1, 2, 3])
b = np.array([6, 5, 4])
k = 2

print 'Вектор a:', a
print 'Вектор b:', b
print 'Число k:', k


Вектор a: [1 2 3]
Вектор b: [6 5 4]
Число k: 2

In [11]:
print 'Сумма a и b:\n', a + b


Сумма a и b:
[7 7 7]

In [12]:
print 'Разность a и b:\n', a - b


Разность a и b:
[-5 -3 -1]

In [13]:
print 'Покоординатное умножение a и b:\n', a * b


Покоординатное умножение a и b:
[ 6 10 12]

In [14]:
print 'Умножение вектора на число (осуществляется покоординатно):\n', k * a


Умножение вектора на число (осуществляется покоординатно):
[2 4 6]

3. Нормы векторов

Вспомним некоторые нормы, которые можно ввести в пространстве $\mathbb{R}^{n}$, и рассмотрим, с помощью каких библиотек и функций их можно вычислять в NumPy.

p-норма

p-норма (норма Гёльдера) для вектора $x = (x_{1}, \dots, x_{n}) \in \mathbb{R}^{n}$ вычисляется по формуле:

$$ \left\Vert x \right\Vert_{p} = \left( \sum_{i=1}^n \left| x_{i} \right|^{p} \right)^{1 / p},~p \geq 1. $$

В частных случаях при:

  • $p = 1$ получаем $\ell_{1}$ норму
  • $p = 2$ получаем $\ell_{2}$ норму

Далее нам понабится модуль numpy.linalg, реализующий некоторые приложения линейной алгебры. Для вычисления различных норм мы используем функцию numpy.linalg.norm(x, ord=None, ...), где x — исходный вектор, ord — параметр, определяющий норму (мы рассмотрим два варианта его значений — 1 и 2). Импортируем эту функцию:


In [15]:
from numpy.linalg import norm

$\ell_{1}$ норма

$\ell_{1}$ норма (также известная как манхэттенское расстояние) для вектора $x = (x_{1}, \dots, x_{n}) \in \mathbb{R}^{n}$ вычисляется по формуле:

$$ \left\Vert x \right\Vert_{1} = \sum_{i=1}^n \left| x_{i} \right|. $$

Ей в функции numpy.linalg.norm(x, ord=None, ...) соответствует параметр ord=1.


In [16]:
a = np.array([1, 2, -3])
print 'Вектор a:', a


Вектор a: [ 1  2 -3]

In [17]:
print 'L1 норма вектора a:\n', norm(a, ord=1)


L1 норма вектора a:
6

$\ell_{2}$ норма

$\ell_{2}$ норма (также известная как евклидова норма) для вектора $x = (x_{1}, \dots, x_{n}) \in \mathbb{R}^{n}$ вычисляется по формуле:

$$ \left\Vert x \right\Vert_{2} = \sqrt{\sum_{i=1}^n \left( x_{i} \right)^2}. $$

Ей в функции numpy.linalg.norm(x, ord=None, ...) соответствует параметр ord=2.


In [18]:
a = np.array([1, 2, -3])
print 'Вектор a:', a


Вектор a: [ 1  2 -3]

In [19]:
print 'L2 норма вектора a:\n', norm(a, ord=2)


L2 норма вектора a:
3.74165738677

Более подробно о том, какие еще нормы (в том числе матричные) можно вычислить, см. документацию.

4. Расстояния между векторами

Для двух векторов $x = (x_{1}, \dots, x_{n}) \in \mathbb{R}^{n}$ и $y = (y_{1}, \dots, y_{n}) \in \mathbb{R}^{n}$ $\ell_{1}$ и $\ell_{2}$ раccтояния вычисляются по следующим формулам соответственно:

$$ \rho_{1}\left( x, y \right) = \left\Vert x - y \right\Vert_{1} = \sum_{i=1}^n \left| x_{i} - y_{i} \right| $$$$ \rho_{2}\left( x, y \right) = \left\Vert x - y \right\Vert_{2} = \sqrt{\sum_{i=1}^n \left( x_{i} - y_{i} \right)^2}. $$

In [20]:
a = np.array([1, 2, -3])
b = np.array([-4, 3, 8])
print 'Вектор a:', a
print 'Вектор b:', b


Вектор a: [ 1  2 -3]
Вектор b: [-4  3  8]

In [21]:
print 'L1 расстояние между векторами a и b:\n', norm(a - b, ord=1)


L1 расстояние между векторами a и b:
17

In [22]:
print 'L2 расстояние между векторами a и b:\n', norm(a - b, ord=2)


L2 расстояние между векторами a и b:
12.124355653

Также расстояние между векторами можно посчитать с помощью функции scipy.spatial.distance.cdist(XA, XB, metric='euclidean', p=2, ...) из модуля SciPy, предназначенного для выполнения научных и инженерных расчётов.


In [23]:
from scipy.spatial.distance import cdist

scipy.spatial.distance.cdist(...) требует, чтобы размерность XA и XB была как минимум двумерная. По этой причине для использования этой функции необходимо преобразовать векторы, которые мы рассматриваем в этом ноутбуке, к вектор-строкам с помощью способов, которые мы рассмотрим ниже.

Параметры XA, XB — исходные вектор-строки, а metric и p задают метрику расстояния (более подробно о том, какие метрики можно использовать, см. документацию).

Первый способ из вектора сделать вектор-строку (вектор-столбец) — это использовать метод array.reshape(shape), где параметр shape задает размерность вектора (кортеж чисел).


In [24]:
a = np.array([6, 3, -5])
b = np.array([-1, 0, 7])
print 'Вектор a:', a
print 'Его размерность:', a.shape
print 'Вектор b:', b
print 'Его размерность:', b.shape


Вектор a: [ 6  3 -5]
Его размерность: (3,)
Вектор b: [-1  0  7]
Его размерность: (3,)

In [25]:
a = a.reshape((1, 3))
b = b.reshape((1, 3))
print 'После применения метода reshape:\n'
print 'Вектор-строка a:', a
print 'Его размерность:', a.shape
print 'Вектор-строка b:', b
print 'Его размерность:', b.shape


После применения метода reshape:

Вектор-строка a: [[ 6  3 -5]]
Его размерность: (1, 3)
Вектор-строка b: [[-1  0  7]]
Его размерность: (1, 3)

In [26]:
print 'Манхэттенское расстояние между a и b (через cdist):', cdist(a, b, metric='cityblock')


Манхэттенское расстояние между a и b (через cdist): [[ 22.]]

Заметим, что после применения этого метода размерность полученных вектор-строк будет равна shape. Следующий метод позволяет сделать такое же преобразование, но не изменяет размерность исходного вектора.

В NumPy к размерностям объектов можно добавлять фиктивные оси с помощью np.newaxis. Для того, чтобы понять, как это сделать, рассмотрим пример:


In [27]:
d = np.array([3, 0, 8, 9, -10])
print 'Вектор d:', d
print 'Его размерность:', d.shape


Вектор d: [  3   0   8   9 -10]
Его размерность: (5,)

In [28]:
print 'Вектор d с newaxis --> вектор-строка:\n', d[np.newaxis, :]
print 'Полученная размерность:', d[np.newaxis, :].shape

print 'Вектор d с newaxis --> вектор-столбец:\n', d[:, np.newaxis]
print 'Полученная размерность:', d[:, np.newaxis].shape


Вектор d с newaxis --> вектор-строка:
[[  3   0   8   9 -10]]
Полученная размерность: (1, 5)
Вектор d с newaxis --> вектор-столбец:
[[  3]
 [  0]
 [  8]
 [  9]
 [-10]]
Полученная размерность: (5, 1)

Важно, что np.newaxis добавляет к размерности ось, длина которой равна 1 (это и логично, так как количество элементов должно сохраняться). Таким образом, надо вставлять новую ось там, где нужна единица в размерности.

Теперь посчитаем расстояния с помощью scipy.spatial.distance.cdist(...), используя np.newaxis для преобразования векторов:


In [29]:
a = np.array([6, 3, -5])
b = np.array([-1, 0, 7])
print 'Евклидово расстояние между a и b (через cdist):', cdist(a[np.newaxis, :], 
                                                               b[np.newaxis, :], 
                                                               metric='euclidean')


Евклидово расстояние между a и b (через cdist): [[ 14.2126704]]

Эта функция также позволяет вычислять попарные расстояния между множествами векторов. Например, пусть у нас имеется матрица размера $m_{A} \times n$. Мы можем рассматривать ее как описание некоторых $m_{A}$ наблюдений в $n$-мерном пространстве. Пусть также имеется еще одна аналогичная матрица размера $m_{B} \times n$, где $m_{B}$ векторов в том же $n$-мерном пространстве. Часто необходимо посчитать попарные расстояния между векторами первого и второго множеств. В этом случае можно пользоваться функцией scipy.spatial.distance.cdist(XA, XB, metric='euclidean', p=2, ...), где в качестве XA, XB необходимо передать две описанные матрицы. Функция возвращает матрицу попарных расстояний размера $m_{A} \times m_{B}$, где элемент матрицы на $[i, j]$-ой позиции равен расстоянию между $i$-тым вектором первого множества и $j$-ым вектором второго множества.

В данном случае эта функция предподчительнее numpy.linalg.norm(...), так как она вычисляет попарные расстояния быстрее и эффективнее.

5. Скалярное произведение и угол между векторами


In [30]:
a = np.array([0, 5, -1])
b = np.array([-4, 9, 3])
print 'Вектор a:', a
print 'Вектор b:', b


Вектор a: [ 0  5 -1]
Вектор b: [-4  9  3]

Скалярное произведение в пространстве $\mathbb{R}^{n}$ для двух векторов $x = (x_{1}, \dots, x_{n})$ и $y = (y_{1}, \dots, y_{n})$ определяется как:

$$ \langle x, y \rangle = \sum_{i=1}^n x_{i} y_{i}. $$

Скалярное произведение двух векторов можно вычислять с помощью функции numpy.dot(a, b, ...) или метода vec1.dot(vec2), где vec1 и vec2 — исходные векторы. Также эти функции подходят для матричного умножения, о котором речь пойдет в следующем уроке.


In [31]:
print 'Скалярное произведение a и b (через функцию):', np.dot(a, b)


Скалярное произведение a и b (через функцию): 42

In [32]:
print 'Скалярное произведение a и b (через метод):', a.dot(b)


Скалярное произведение a и b (через метод): 42

Длиной вектора $x = (x_{1}, \dots, x_{n}) \in \mathbb{R}^{n}$ называется квадратный корень из скалярного произведения, то есть длина равна евклидовой норме вектора:

$$ \left| x \right| = \sqrt{\langle x, x \rangle} = \sqrt{\sum_{i=1}^n x_{i}^2} = \left\Vert x \right\Vert_{2}. $$

Теперь, когда мы знаем расстояние между двумя ненулевыми векторами и их длины, мы можем вычислить угол между ними через скалярное произведение:

$$ \langle x, y \rangle = \left| x \right| | y | \cos(\alpha) \implies \cos(\alpha) = \frac{\langle x, y \rangle}{\left| x \right| | y |}, $$

где $\alpha \in [0, \pi]$ — угол между векторами $x$ и $y$.


In [33]:
cos_angle = np.dot(a, b) / norm(a) / norm(b)
print 'Косинус угла между a и b:', cos_angle
print 'Сам угол:', np.arccos(cos_angle)


Косинус угла между a и b: 0.800036283647
Сам угол: 0.643440633609

Более подробно о том, как вычислять скалярное произведение в NumPy, см. документацию.